package cn.monkey.family.server.domain.uc.auth;

import cn.monkey.family.data.Result;
import cn.monkey.family.data.Results;
import cn.monkey.family.data.context.RequestContext;
import cn.monkey.family.data.uc.auth.AuthRequest;
import cn.monkey.family.data.uc.auth.Authorization;
import cn.monkey.family.data.uc.auth.UserSession;
import cn.monkey.family.data.uc.user.User;
import cn.monkey.family.server.domain.uc.auth.verifycode.CodeRepository;
import cn.monkey.family.server.domain.uc.auth.password.PasswordEncoder;
import cn.monkey.family.server.domain.uc.auth.session.UserSessionRepository;
import cn.monkey.family.server.domain.uc.auth.token.RequestContextRepository;
import cn.monkey.family.server.domain.uc.user.UserRepository;
import com.google.common.base.Strings;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;
import java.util.UUID;

@Service
public class AuthService implements IAuthService {
    final UserRepository userRepository;
    final AuthorizeFilterChain authorizeFilterChain;
    final PasswordEncoder passwordEncoder;
    final CodeRepository codeRepository;
    final UserSessionRepository userSessionRepository;
    final RequestContextRepository requestContextRepository;
    final IAfterAuthorizeHandler afterAuthorizeHandler;

    public AuthService(UserRepository userRepository,
                       AuthorizeFilterChain authorizeFilterChain,
                       UserSessionRepository userSessionRepository,
                       PasswordEncoder passwordEncoder,
                       CodeRepository codeRepository,
                       RequestContextRepository requestContextRepository,
                       IAfterAuthorizeHandler afterAuthorizeHandler) {
        this.userRepository = userRepository;
        this.authorizeFilterChain = authorizeFilterChain;
        this.passwordEncoder = passwordEncoder;
        this.codeRepository = codeRepository;
        this.userSessionRepository = userSessionRepository;
        this.requestContextRepository = requestContextRepository;
        this.afterAuthorizeHandler = afterAuthorizeHandler;
    }


    @Override
    public Result<Authorization> authorize(RequestContext requestContext, AuthRequest authRequest) {
        String account = authRequest.getAccount();
        if (Strings.isNullOrEmpty(account)) {
            return Results.fail("ACCOUNT_IS_REQUIRED");
        }
        List<User> users = this.userRepository.findByAccountOrMobileOrEmail(account);
        Optional<User> userOptional = this.tryFindUser(users, authRequest);
        if (userOptional.isEmpty()) {
            return Results.fail("INVALID_ACCOUNT_OR_PASSWORD");
        }
        try {
            this.authorizeFilterChain.doFilter(requestContext, authRequest);
        } catch (AuthorizeException e) {
            return Results.fail(e.getMessage());
        }
        return this.afterAuthorize(requestContext, userOptional.get());
    }

    private Optional<User> tryFindUser(List<User> users, AuthRequest authRequest) {
        String password = authRequest.getPassword();
        if (!Strings.isNullOrEmpty(password)) {
            return users.stream().filter(user -> this.passwordEncoder.matches(password, user.getPassword())).findFirst();
        }
        String account = authRequest.getAccount();
        Optional<User> userOptional = users.stream()
                .filter(user -> account.equals(user.getMobile()))
                .findFirst();
        if (userOptional.isEmpty()) {
            return userOptional;
        }
        String verifyCode = authRequest.getCode();
        if (!Strings.isNullOrEmpty(verifyCode) && verifyCode.equals(this.codeRepository.getCode(account))) {
            return userOptional;
        }
        return Optional.empty();
    }

    private Result<Authorization> afterAuthorize(RequestContext requestContext, User user) {
        return this.afterAuthorizeHandler.handle(requestContext, user);
    }

    @Override
    public Result<Void> clearAuth(RequestContext requestContext) {
        UserSession userSession = requestContext.getUserSession();
        if (userSession == null) {
            return Results.ok();
        }
        this.userSessionRepository.delete(userSession.getUid());
        return Results.ok();
    }

    @Override
    public Result<String> newTemporaryToken(RequestContext requestContext) {
        String token = UUID.randomUUID().toString();
        this.requestContextRepository.set(token, requestContext);
        return Results.ok(token);
    }
}
